// Package web
/**
* @Project : geektime-basic-go-study
* @File    : user.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/9/4 13:24
**/

package web

import (
	"errors"
	"fmt"
	"geektime-basic-go-study/webook/backend/internal/domain"
	"geektime-basic-go-study/webook/backend/internal/service"
	ijwt "geektime-basic-go-study/webook/backend/internal/web/jwt"
	"github.com/HJH0924/GenericGo/logger"
	"github.com/dlclark/regexp2"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	"net/http"
	"time"
)

// 登录业务
const bizLogin = "login"

var _ Handler = (*UserHandler)(nil)

// UserHandler
// 所有和用户有关的路由都定义在这个 Handler 上
type UserHandler struct {
	userSvc service.UserService
	codeSvc service.CodeService
	// 初始化的时候预编译正则表达式
	emailExp    *regexp2.Regexp
	passwordExp *regexp2.Regexp
	phoneExp    *regexp2.Regexp
	logger.Logger
	ijwt.UserJWTHandler
}

func NewUserHandler(userSvc service.UserService, codeSvc service.CodeService, zl logger.Logger, ujwtHdl ijwt.UserJWTHandler) *UserHandler {
	const (
		emailRegexPattern    = `^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$`
		passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$`
		phoneRegexPattern    = `^1[3-9]\d{9}$`
	)
	return &UserHandler{
		userSvc:        userSvc,
		codeSvc:        codeSvc,
		emailExp:       regexp2.MustCompile(emailRegexPattern, regexp2.None),
		passwordExp:    regexp2.MustCompile(passwordRegexPattern, regexp2.None),
		phoneExp:       regexp2.MustCompile(phoneRegexPattern, regexp2.None),
		Logger:         zl,
		UserJWTHandler: ujwtHdl,
	}
}

func (Self *UserHandler) RegisterRoutes(server *gin.Engine) {
	ug := server.Group("/users")
	ug.POST("/signup", Self.SignUp)
	//ug.POST("/login", Self.Login)
	ug.POST("/login", Self.LoginJWT)
	ug.POST("/logout", Self.LogoutJWT)
	ug.POST("/edit", Self.Edit)
	ug.GET("/profile", Self.Profile)
	ug.POST("/login_sms/code/send", Self.SendLoginSMSCode)
	ug.POST("/login_sms", Self.LoginSMS)
	ug.POST("/refresh_access_token", Self.RefreshAccessToken)
}

func (Self *UserHandler) checkValidEmail(email string) (bool, string) {
	ok, err := Self.emailExp.MatchString(email)
	if err != nil {
		Self.Debug("UserHandler.checkValidEmail Failed", logger.Field{Key: "error", Val: err})
		return false, "系统错误"
	}
	if !ok {
		return false, "邮箱格式不匹配"
	} else {
		return true, ""
	}
}

func (Self *UserHandler) checkValidPassword(password string) (bool, string) {
	ok, err := Self.passwordExp.MatchString(password)
	if err != nil {
		Self.Debug("UserHandler.checkValidPassword Failed", logger.Field{Key: "error", Val: err})
		return false, "系统错误"
	}
	if !ok {
		return false, "密码必须包含数字、特殊字符，并且长度不能小于 8 位"
	} else {
		return true, ""
	}
}

func (Self *UserHandler) checkValidPhone(phone string) (bool, string) {
	ok, err := Self.phoneExp.MatchString(phone)
	if err != nil {
		Self.Debug("UserHandler.checkValidPhone Failed", logger.Field{Key: "error", Val: err})
		return false, "系统错误"
	}
	if !ok {
		return false, "中国大陆手机号必须是1开头，第二位是3至9之间的数字，后面跟着9位任意数字"
	} else {
		return true, ""
	}
}

func (Self *UserHandler) checkPassword(password string, confirmPassword string) (bool, string) {
	if confirmPassword != password {
		return false, "两次输入密码不一致"
	}
	return Self.checkValidPassword(password)
}

func (Self *UserHandler) SignUp(ctx *gin.Context) {
	type SignUpReq struct {
		Email           string `json:"email"`
		ConfirmPassword string `json:"confirmPassword"`
		Password        string `json:"password"`
	}

	var req SignUpReq
	// Bind 方法会根据 Content-Type 来解析前端过来的数据到 req 里面
	// 解析错了，就会直接写回一个 400 的错误
	// http.StatusBadRequest 400
	if err := ctx.BindJSON(&req); err != nil {
		Self.Debug("UserHandler.SignUp BindJSON Failed", logger.Field{Key: "error", Val: err})
		return
	}

	res, msg := Self.checkValidEmail(req.Email)
	if !res {
		ctx.JSON(http.StatusOK, Result{
			Code: ClientErrNum,
			Msg:  msg,
		})
		return
	}
	res, msg = Self.checkPassword(req.Password, req.ConfirmPassword)
	if !res {
		ctx.JSON(http.StatusOK, Result{
			Code: ClientErrNum,
			Msg:  msg,
		})
		return
	}

	err := Self.userSvc.SignUp(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})

	if err != nil {
		if errors.Is(err, service.ErrUserDuplicate) {
			ctx.JSON(http.StatusOK, Result{
				Code: ClientErrNum,
				Msg:  "已存在该用户",
			})
			return
		} else {
			Self.Debug("UserHandler.SignUp Failed", logger.Field{Key: "error", Val: err})
			ctx.JSON(http.StatusOK, Result{
				Code: ServerErrNum,
				Msg:  "系统错误",
			})
			return
		}
	}

	ctx.JSON(http.StatusOK, Result{
		Msg: "注册成功",
	})
}

// Login
// Deprecated
// 一个是验证登录
// 一个是其他接口的登录态校验，部分接口需要登录之后才能使用
func (Self *UserHandler) Login(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	var req LoginReq
	if err := ctx.BindJSON(&req); err != nil {
		return
	}

	// 不需要去对邮箱或者密码的格式去做校验
	//res, msg := Self.checkValidEmail(req.Email)
	//if !res {
	//	ctx.String(http.StatusOK, msg)
	//	return
	//}
	//
	//res, msg = Self.checkValidPassword(req.Password)
	//if !res {
	//	ctx.String(http.StatusOK, msg)
	//	return
	//}

	u, err := Self.userSvc.Login(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})

	if err != nil {
		if errors.Is(err, service.ErrInvalidEmailOrPassword) {
			ctx.JSON(http.StatusOK, Result{
				Code: ClientErrNum,
				Msg:  "邮箱或密码错误",
			})
			return
		} else {
			ctx.JSON(http.StatusOK, Result{
				Code: ServerErrNum,
				Msg:  "系统错误",
			})
			return
		}
	}

	// 登录成功之后，需要设置sessionID放在cookie里面并返回给前端
	// 然后登录成功之后，前端就需要带着sessionID来请求某些需要登录才能使用的接口
	session := sessions.Default(ctx)
	session.Set("UserId", u.Id)
	// ↓这个配置是设置cookie的配置
	session.Options(sessions.Options{
		Secure:   viper.GetBool("config.secure"),
		HttpOnly: viper.GetBool("config.httpOnly"),
		//Secure:   true, // 生产环境开启
		//HttpOnly: true, // 生产环境开启
		MaxAge: 60, // 一分钟过期
	})
	if err = session.Save(); err != nil {
		ctx.JSON(http.StatusOK, Result{
			Code: ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}

	ctx.JSON(http.StatusOK, Result{
		Msg: "登录成功",
	})
	return
}

// LoginJWT
// 一个是验证登录
// 一个是其他接口的登录态校验，部分接口需要登录之后才能使用
func (Self *UserHandler) LoginJWT(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	var req LoginReq
	if err := ctx.BindJSON(&req); err != nil {
		Self.Debug("UserHandler.LoginJWT BindJSON Failed", logger.Field{Key: "error", Val: err})
		return
	}

	u, err := Self.userSvc.Login(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})

	if err != nil {
		if errors.Is(err, service.ErrInvalidEmailOrPassword) {
			ctx.JSON(http.StatusOK, Result{
				Code: ClientErrNum,
				Msg:  "邮箱或密码错误",
			})
			return
		} else {
			Self.Debug("UserHandler.LoginJWT Failed", logger.Field{Key: "error", Val: err})
			ctx.JSON(http.StatusOK, Result{
				Code: ServerErrNum,
				Msg:  "系统错误",
			})
			return
		}
	}

	// 在这里用 JWT 设置登录态
	if err := Self.SetAccessAndRefreshJWT(ctx, u.Id); err != nil {
		Self.Debug("UserHandler.LoginJWT Failed", logger.Field{Key: "jwt error", Val: err})
		ctx.JSON(http.StatusInternalServerError, Result{
			Code: ServerErrNum,
			Msg:  "jwt error",
		})
		return
	}
	fmt.Println(u)
	ctx.JSON(http.StatusOK, Result{
		Msg: "登录成功",
	})
	return
}

func (Self *UserHandler) LogoutJWT(ctx *gin.Context) {
	err := Self.InvalidateJWT(ctx)
	if err != nil {
		Self.Debug("UserHandler.LogoutJWT Failed", logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, Result{
			Code: ServerErrNum,
			Msg:  "退出登录失败",
		})
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "退出登录成功",
	})
}

func (Self *UserHandler) Edit(ctx *gin.Context) {
	type Req struct {
		// 注意，其它字段，尤其是密码、邮箱和手机，
		// 修改都要通过其他手段，邮箱和手机都要验证
		// 密码更不用说
		Nickname string `json:"nickname"`
		Birthday string `json:"birthday"`
		AboutMe  string `json:"aboutMe"`
	}

	var req Req
	if err := ctx.BindJSON(&req); err != nil {
		Self.Debug("UserHandler.Edit BindJSON Failed", logger.Field{Key: "error", Val: err})
		return
	}
	// 可以在这里校验，具体校验规则问产品
	if ok, msg := Self.checkValidNickname(req.Nickname); !ok {
		ctx.JSON(http.StatusOK, Result{Code: ClientErrNum, Msg: msg})
		return
	}
	if ok, msg := Self.checkValidAboutMe(req.AboutMe); !ok {
		ctx.JSON(http.StatusOK, Result{Code: ClientErrNum, Msg: msg})
		return
	}
	birthday, err := time.Parse(time.DateOnly, req.Birthday) // valid format: 2006-01-02
	if err != nil {
		Self.Debug("UserHandler.Edit time.parse Failed", logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, Result{Code: ClientErrNum, Msg: "日期格式错误"})
		return
	}

	claims, _ := ctx.Get("UserClaims")
	// 这里是肯定可以get到的，所以不需要判断是否get到
	userClaims, ok := claims.(*ijwt.UserClaims)
	if !ok {
		// 在日志中包含更多的上下文信息，例如原始的 claims 值和类型
		Self.Debug("UserHandler.Edit: Failed to parse UserClaims",
			logger.Field{Key: "success", Val: ok},                             // 记录ok的值
			logger.Field{Key: "claimsType", Val: fmt.Sprintf("%T", claims)},   // 包含 claims 的类型
			logger.Field{Key: "claimsValue", Val: fmt.Sprintf("%+v", claims)}, // 包含 claims 的值
			logger.Field{Key: "context", Val: ctx.Request.Context()},          // 记录请求的上下文
		)
		ctx.JSON(http.StatusOK, Result{Code: ServerErrNum, Msg: "系统错误"})
		return
	}

	if err := Self.userSvc.UpdateNonSensitiveInfo(ctx, domain.User{
		Id:       userClaims.Uid,
		Nickname: req.Nickname,
		Birthday: birthday,
		AboutMe:  req.AboutMe,
	}); err != nil {
		Self.Debug("UserHandler.Edit: UpdateNonSensitiveInfo Failed", logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, Result{Code: ServerErrNum, Msg: "系统错误"})
		return
	}

	ctx.JSON(http.StatusOK, Result{Msg: "修改成功"})
	return
}

func (Self *UserHandler) checkValidNickname(nickname string) (bool, string) {
	if nickname == "" {
		return false, "昵称不能为空"
	}
	return true, ""
}

func (Self *UserHandler) checkValidAboutMe(aboutMe string) (bool, string) {
	if len(aboutMe) > 1024 {
		return false, "自我介绍过长"
	}
	return true, ""
}

func (Self *UserHandler) Profile(ctx *gin.Context) {
	type Profile struct {
		Email    string `json:"email"`
		Phone    string `json:"phone"`
		Nickname string `json:"nickname"`
		Birthday string `json:"birthday"`
		AboutMe  string `json:"aboutMe"`
	}
	claims, _ := ctx.Get("UserClaims")
	// 这里是肯定可以get到的，所以不需要判断是否get到
	userClaims, ok := claims.(*ijwt.UserClaims)
	if !ok {
		// 在日志中包含更多的上下文信息，例如原始的 claims 值和类型
		Self.Debug("UserHandler.Profile: Failed to parse UserClaims",
			logger.Field{Key: "success", Val: ok},                             // 记录ok的值
			logger.Field{Key: "claimsType", Val: fmt.Sprintf("%T", claims)},   // 包含 claims 的类型
			logger.Field{Key: "claimsValue", Val: fmt.Sprintf("%+v", claims)}, // 包含 claims 的值
			logger.Field{Key: "context", Val: ctx.Request.Context()},          // 记录请求的上下文
		)
		ctx.JSON(http.StatusOK, Result{Code: ServerErrNum, Msg: "系统错误"})
		return
	}
	fmt.Println(userClaims.Uid) // for debug
	user, err := Self.userSvc.Profile(ctx, userClaims.Uid)
	if err != nil {
		Self.Debug("UserHandler.Profile: Service Profile Failed", logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, Result{Code: ServerErrNum, Msg: "系统错误"})
		return
	}

	var birthday string
	if !user.Birthday.IsZero() {
		birthday = user.Birthday.Format(time.DateOnly)
	}
	// ↓需要修改前端对应解析代码
	//ctx.JSON(http.StatusOK, Result{
	//	Msg: "获取成功",
	//	Data: Profile{
	//		Email:    user.Email,
	//		Phone:    user.Phone,
	//		Nickname: user.Nickname,
	//		Birthday: birthday,
	//		AboutMe:  user.AboutMe,
	//	},
	//})
	ctx.JSON(http.StatusOK, Profile{
		Email:    user.Email,
		Phone:    user.Phone,
		Nickname: user.Nickname,
		Birthday: birthday,
		AboutMe:  user.AboutMe,
	})
}

func (Self *UserHandler) SendLoginSMSCode(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
	}

	var req Req
	if err := ctx.BindJSON(&req); err != nil {
		Self.Debug("UserHandler.SendLoginSMSCode: BindJSON Failed", logger.Field{Key: "error", Val: err})
		return
	}

	// 校验手机号格式
	ok, msg := Self.checkValidPhone(req.Phone)
	if !ok {
		ctx.JSON(http.StatusOK, Result{
			Code: ClientErrNum,
			Msg:  msg,
		})
		return
	}

	code, err := Self.codeSvc.Send(ctx, bizLogin, req.Phone)
	if err != nil {
		Self.Debug("UserHandler.SendLoginSMSCode: Send Failed", logger.Field{Key: "error", Val: err})
	}

	switch {
	case err == nil:
		ctx.JSON(http.StatusOK, Result{
			Msg: fmt.Sprintf("验证码：%s", code),
		})
		return
	case errors.Is(err, service.ErrCodeSendTooMany):
		ctx.JSON(http.StatusOK, Result{
			Code: ClientErrNum,
			Msg:  "验证码发送太频繁",
		})
		return
	default:
		ctx.JSON(http.StatusOK, Result{
			Code: ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}
}

func (Self *UserHandler) LoginSMS(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
		Code  string `json:"code"`
	}

	var req Req
	err := ctx.BindJSON(&req)
	if err != nil {
		Self.Debug("UserHandler.LoginSMS: BindJSON Failed", logger.Field{Key: "error", Val: err})
		return
	}

	// 校验手机号格式
	ok, msg := Self.checkValidPhone(req.Phone)
	if !ok {
		ctx.JSON(http.StatusOK, Result{
			Code: ClientErrNum,
			Msg:  msg,
		})
		return
	}
	// 校验验证码
	ok, err = Self.codeSvc.Verify(ctx, bizLogin, req.Phone, req.Code)
	if err != nil {
		if errors.Is(err, service.ErrCodeVerifyTooMany) {
			ctx.JSON(http.StatusOK, Result{
				Code: ClientErrNum,
				Msg:  "验证码验证次数过多",
			})
		} else {
			Self.Debug("UserHandler.LoginSMS: Verify Failed", logger.Field{Key: "error", Val: err})
			ctx.JSON(http.StatusOK, Result{
				Code: ServerErrNum,
				Msg:  "系统错误",
			})
		}
		return
	} else {
		// err == nil
		if !ok {
			ctx.JSON(http.StatusOK, Result{
				Code: ClientErrNum,
				Msg:  "验证失败",
			})
			return
		}
	}
	// err == nil && ok == true
	// 此时如果需要通过手机号查询该用户，然后设置jwt token，来设置登录态
	// 如果该用户不存在，则创建该用户（即注册）
	u, err := Self.userSvc.FindOrCreateByPhone(ctx, req.Phone)
	if err != nil {
		Self.Debug("UserHandler.LoginSMS: FindOrCreateByPhone Failed", logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, Result{
			Code: ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}
	if err := Self.SetAccessAndRefreshJWT(ctx, u.Id); err != nil {
		Self.Debug("UserHandler.LoginSMS: SetAccessAndRefreshJWT Failed", logger.Field{Key: "error", Val: err})
		ctx.JSON(http.StatusOK, Result{
			Code: ServerErrNum,
			Msg:  "系统错误",
		})
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "验证成功",
	})
	return
}

func (Self *UserHandler) RefreshAccessToken(ctx *gin.Context) {
	err := Self.RefreshAccessJWT(ctx)
	if err != nil {
		Self.Debug("UserHandler.RefreshAccessToken: RefreshAccessJWT Failed", logger.Field{Key: "error", Val: err})
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	ctx.JSON(http.StatusOK, Result{
		Msg: "刷新成功",
	})
}
